Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow redirect to MagicLink after Invalid Validation Code #5150

Merged
merged 25 commits into from
Jan 3, 2022

Conversation

akshayasalvi
Copy link
Contributor

@akshayasalvi akshayasalvi commented Sep 8, 2021

@chiragsalian Can you please review?

Details

  • Show link expired message and allow resending the magic link

Fixed Issues

$ #4737

Tests

  1. Tested by setting a password for expired tokens
  2. Tested by invalid logins without user email

QA Steps

  1. Go to the login page and enter the email/phone
  2. Select Forgot option
  3. Visit the received magic link. It should have a accountId and validationCode new.expensify.com/<accountId>/<code>
    Example URL : http://localhost:8080/setpassword/10504929/FGVGEKKUJ
  4. Update the code to any invalid code.
  5. Enter the password
  6. It should redirect to the magic link page with the updated message

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

Screenshots

Web

Screenshot 2021-09-11 at 6 50 59 PM

v2

image

Mobile Web

Screenshot 2021-09-11 at 7 04 32 PM

Desktop

iOS

Android

@akshayasalvi
Copy link
Contributor Author

Lint error as the PR is WIP and I've added some console to test

@chiragsalian
Copy link
Contributor

Moving your question posted here. Since its more technical i think it will be better discussed here.

So this part,

After the redirect, I can see in the console that only showResendValidationLinkForm is true yet it doesn't show the MagicLink page. It shows the Password page only. Once I do a refresh from the address bar it shows the magic link page.

I tested your code. So firstly i don't see where you're doing the redirect. For example here is the url i used - http://localhost:8080/setpassword/10/DWGZCFRKT. When i entered the new password i noticed the API failed which is expected. After this there are two parts to address that i believe your code hasn't captured,

  1. On this line this.props.credentials.login has no value and hence showResendValidationLinkForm will remain undefined.
  2. The URL contiunes to stay with setpassword/10/DWGZCFRKT so the setPassword component is not unmounted and hence you're not getting the desired affect.

To solve these,
For 1: We'll firstly the API should return email. It currently does not. I'll push up a PR so that it returns email. But after that you need to set credentials.login after this line.
For 2: At the end of this block you'll need to do a redirect to unmount setPassword i.e., you should do Navigation.navigate(ROUTES.HOME);.

Let me know if those make sense.

@akshayasalvi
Copy link
Contributor Author

Hi, Thanks for the response.

this.props.credentials.login has no value
Yeah, I am guessing it is because I was manually hitting setpassword it caused an issue.

To solve these,
For 1: We'll firstly the API should return email. It currently does not. I'll push up a PR so that it returns email. But after that you need to set credentials.login after this line.

Okay I'll update the credentials in Onyx. But one addition would be if you see currently it is going to the catch block. If you're going to send the email in the response you'll have to probably send success along with the jsonCode like the other APIs, right?

For 2: At the end of this block you'll need to do a redirect to unmount setPassword i.e., you should do Navigation.navigate(ROUTES.HOME);.

I did put Navigation.navigate in the catch block. But removed it to see if it was causing an issue. I thought calling the same route was causing it.

@chiragsalian
Copy link
Contributor

But one addition would be if you see currently it is going to the catch block. If you're going to send the email in the response you'll have to probably send success along with the jsonCode like the other APIs, right?

No i don't have to send success just to send data. If you check response in catch block even now it has data. It just doesn't have the email which we should pass so you can use it.

@akshayasalvi
Copy link
Contributor Author

You're right. Just saw. Makes sense. Thanks 👍

@akshayasalvi
Copy link
Contributor Author

Hi @chiragsalian Let me know once the API is ready and I'll finish this today.

@chiragsalian
Copy link
Contributor

Sorry, i didn't get to this today, but its on my plate to work on for tomorrow. Once i create the return param i can test it out with your PR but even if my code is merged tomorrow it would take 3 business days before its live and we can merge your PR. Just letting you know.

@akshayasalvi
Copy link
Contributor Author

Sorry, i didn't get to this today, but its on my plate to work on for tomorrow. Once i create the return param i can test it out with your PR but even if my code is merged tomorrow it would take 3 business days before its live and we can merge your PR. Just letting you know.

Should I then assume that I receive the param and then update this PR so that it is easier for you to test?

@chiragsalian
Copy link
Contributor

Should I then assume that I receive the param and then update this PR so that it is easier for you to test?

Yes definitely.

@chiragsalian
Copy link
Contributor

I've made the change. Just so you know, in the catch statement if you're passing response. The email will be under response.data.email. I'll put out my code for review now. I expect it would be live for you to use on tuesday or wednesday. You can still code out the remaining pieces and we can review it so its ready to merge whenever my code is live.

@akshayasalvi
Copy link
Contributor Author

Thanks I’ll update the code over the weekend.

@akshayasalvi akshayasalvi marked this pull request as ready for review September 12, 2021 04:17
@akshayasalvi akshayasalvi requested a review from a team as a code owner September 12, 2021 04:17
@MelvinBot MelvinBot requested review from marcaaron and removed request for a team September 12, 2021 04:17
@akshayasalvi
Copy link
Contributor Author

@chiragsalian I've updated the PR and manually tested the flow.

I've got one query though. The data param in the API response looks like an array but you've mentioned the key as response.data.email. So can you confirm the key once and then accordingly I'll update if required.

@@ -344,6 +344,7 @@ export default {
linkHasBeenResent: 'El enlace se ha reenviado',
weSentYouMagicSignInLink: ({loginType}) => `Hemos enviado un enlace mágico de inicio de sesión a tu ${loginType}.`,
resendLink: 'Reenviar enlace',
validationCodeFailedMessage: 'Parece que ha habido un error con tu enlace de validación. O bien el enlace de validación ha caducado, o su cuenta no existe.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking but did we run this translation past the Español team here at Expensify?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this copy wasn't confirmed. I reached out to the Español team and will let you guys know once i get copy confirmation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy i received that we should use here is,

Parece que hubo un error con el enlace de validación. El enlace de validación ha caducado o tu cuenta no existe.

@@ -126,6 +127,7 @@ function fetchAccountDetails(login) {
validated: response.validated,
closed: response.isClosed,
forgotPassword: false,
validationCodeFailedMessage: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is the best way to handle these errors.

It looks like this will set the translation key for the error message?

I think it would be better to make this an object with a key of errors rather than have an object key per error. We are using a similar strategy in other forms so it would be a good idea to align on this and keep things consistent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to marc a bit about this let's actually change this to a boolean validateCodeExpired: false here and not save translations keys.

.finally(() => {
}).catch((errResponse) => {
const login = lodashGet(errResponse, 'data.email', null);
Onyx.merge(ONYXKEYS.ACCOUNT, {validated: false, validationCodeFailedMessage: 'resendValidationForm.validationCodeFailedMessage'});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we display the error in the UI then we can use the correct translation key. I think there's no reason to persist it in device storage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WRt to my previous comment, this now becomes validateCodeExpired: true here.

@@ -63,7 +66,7 @@ class ResendValidationForm extends React.Component {
*/
validateAndSubmitForm() {
this.setState({
formSuccess: this.props.translate('resendValidationForm.linkHasBeenResent'),
formSuccess: this.props.translate(''),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we translating a key of '' ... ?

@@ -34,6 +34,9 @@ const propTypes = {
closed: PropTypes.bool,
}),

/** Title to be shown in the form */
titleMessage: PropTypes.string.isRequired,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of issues with this:

  1. We are not passing a message here we are passing a translation key
  2. It would be better to pass along something like an errors object and then have the form itself define the translation keys to use.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with marc on this one. So yeah we agree this variable name does not represent what it is. In your code its translation keys and not a message so its confusing.

Let's translate in the parent component so that this remains titleMessage and hence keeps the child component clean.

? ''
: this.props.translate(`welcomeText.${showPasswordForm ? 'phrase4' : 'phrase1'}`);

const resendLinkTitleMessage = this.props.account.validationCodeFailedMessage ?? 'resendValidationForm.weSentYouMagicSignInLink';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK we are not widely using the null coalescence operator in our JS code and standardize on lodashGet() for many things. This should be something like

lodashGet(this.props.account, 'validationCodeFailedMessage', 'resendValidationForm.weSentYouMagicSignInLink')

But as mentioned elsewhere ... I don't think it's correct to store the translation key.

Copy link
Contributor

@chiragsalian chiragsalian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the data param in the API response looks like an array but you've mentioned the key as response.data.email

Its an object. So your code i.e., lodashGet(errResponse, 'data.email', null); is good.

@@ -344,6 +344,7 @@ export default {
linkHasBeenResent: 'El enlace se ha reenviado',
weSentYouMagicSignInLink: ({loginType}) => `Hemos enviado un enlace mágico de inicio de sesión a tu ${loginType}.`,
resendLink: 'Reenviar enlace',
validationCodeFailedMessage: 'Parece que ha habido un error con tu enlace de validación. O bien el enlace de validación ha caducado, o su cuenta no existe.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this copy wasn't confirmed. I reached out to the Español team and will let you guys know once i get copy confirmation.

@@ -126,6 +127,7 @@ function fetchAccountDetails(login) {
validated: response.validated,
closed: response.isClosed,
forgotPassword: false,
validationCodeFailedMessage: null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke to marc a bit about this let's actually change this to a boolean validateCodeExpired: false here and not save translations keys.

.finally(() => {
}).catch((errResponse) => {
const login = lodashGet(errResponse, 'data.email', null);
Onyx.merge(ONYXKEYS.ACCOUNT, {validated: false, validationCodeFailedMessage: 'resendValidationForm.validationCodeFailedMessage'});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WRt to my previous comment, this now becomes validateCodeExpired: true here.

@@ -34,6 +34,9 @@ const propTypes = {
closed: PropTypes.bool,
}),

/** Title to be shown in the form */
titleMessage: PropTypes.string.isRequired,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with marc on this one. So yeah we agree this variable name does not represent what it is. In your code its translation keys and not a message so its confusing.

Let's translate in the parent component so that this remains titleMessage and hence keeps the child component clean.

@@ -84,7 +87,7 @@ class ResendValidationForm extends React.Component {
<>
<View>
<Text>
{this.props.translate('resendValidationForm.weSentYouMagicSignInLink', {
{this.props.translate(this.props.titleMessage, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per this comment. let's translate in the parent.

@akshayasalvi
Copy link
Contributor Author

Thanks @marcaaron and @chiragsalian for the comments. I’ll update the PR by tomorrow.

@akshayasalvi
Copy link
Contributor Author

@chiragsalian PR Updated

Copy link
Contributor

@chiragsalian chiragsalian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some minor comments, Rest of the code looks good to me and works well.


const validateCodeExpired = lodashGet(this.props.account, 'validateCodeExpired', false);

const resendLinkTitleMessage = this.props.translate(`resendValidationForm.${validateCodeExpired ? 'validationCodeFailedMessage' : 'weSentYouMagicSignInLink'}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think there is too much here. This line is not very readable and it took me a while to understand why loginType is there for validationCodeFailedMessage (took me a while to realize its not needed for validationCodeFailedMessage but is used just for weSentYouMagicSignInLink)

I personally feel this would be easier read if written like so,

let resendLinkTitleMessage = '';
if (validateCodeExpired) {
    resendLinkTitleMessage = this.props.translate('resendValidationForm.validationCodeFailedMessage');
} else if (showResendValidationLinkForm) {
    resendLinkTitleMessage = this.props.translate('resendValidationForm.weSentYouMagicSignInLink', {
        loginType: (Str.isSMSLogin(this.props.credentials.login)
            ? this.props.translate('common.phoneNumber').toLowerCase()
            : this.props.translate('common.email')).toLowerCase(),
    });
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks. I'll update and raise the PR. Somewhere earlier I had seen that we prefer ternary operator over if-else block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, normally we prefer ternary. This situation is a little different though, with the ternary the operations and arguments involved become hard to follow so I feel like if-else is a lot cleaner here.

@@ -344,6 +344,8 @@ export default {
linkHasBeenResent: 'Link has been re-sent',
weSentYouMagicSignInLink: ({loginType}) => `We've sent a magic sign in link to your ${loginType}.`,
resendLink: 'Resend link',
validationCodeFailedMessage: 'It looks like there was an error with your validation link. Either the validation link has expired, or your account does not exist.',

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unnecessary line break here.

@marcaaron
Copy link
Contributor

What's the status here?

@marcaaron
Copy link
Contributor

Haven't got a response here so thinking maybe we must consider this PR abandoned.
Happy to take over and finish this one up if that works for you @chiragsalian - it was pretty close.

@akshayasalvi
Copy link
Contributor Author

@marcaaron Sorry I somehow missed this one. I've responded to your comment above.

…on-code-expiry

# Conflicts:
#	src/components/WelcomeText.js
#	src/pages/signin/SignInPage.js
@akshayasalvi
Copy link
Contributor Author

@marcaaron PR updated

Onyx.merge(ONYXKEYS.ACCOUNT, {error: Localize.translateLocal('setPasswordPage.accountNotValidated')});
const login = lodashGet(response, 'data.email', null);
Onyx.merge(ONYXKEYS.ACCOUNT, {
validated: true, accountExists: true, validateCodeExpired: true, error: Localize.translateLocal('setPasswordPage.accountNotValidated'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the above change make sense?

It makes sense as a quick solution. However, based on the full conversation in this PR so far - it isn't the one we want and will introduce inconsistency with how we handle errors.

Please try to ask clarifying questions before proposing alternatives, because I'm not sure if we are seeing eye to eye on the problem. I will try to be as specific as possible so we can move on from this PR...

The issue is with setting validated: true.

It seems the only reason we need to set this here is so that the correct error will show here:

const isNewAccount = !this.props.account.accountExists;
const isOldUnvalidatedAccount = this.props.account.accountExists && !this.props.account.validated;
const isSMSLogin = Str.isSMSLogin(this.props.credentials.login);
const login = isSMSLogin ? this.props.toLocalPhone(Str.removeSMSDomain(this.props.credentials.login)) : this.props.credentials.login;
const loginType = (isSMSLogin ? this.props.translate('common.phone') : this.props.translate('common.email')).toLowerCase();
let message = '';
if (isNewAccount) {
message = this.props.translate('resendValidationForm.newAccount', {
login,
loginType,
});
} else if (isOldUnvalidatedAccount) {
message = this.props.translate('resendValidationForm.unvalidatedAccount');
} else if (this.props.account.validateCodeExpired) {
message = this.props.translate('resendValidationForm.validationCodeFailedMessage');

The isOldUnvalidatedAccount variable needs to be false and I suspect that's only reason we are setting it to true. But it is not the correct state for the account. We must change the logic that shows the errors not the state of the account.

src/libs/actions/Session/index.js Outdated Show resolved Hide resolved
@akshayasalvi
Copy link
Contributor Author

@marcaaron I've updated the PR. Hope this does make sense. Thanks for being patient on this one. It has gone through multiple iterations and is a lot different from the initial changes. Appreciate your help. Will make any changes if we're still on a different page.

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @akshayasalvi, doesn't seem like my feedback was understood so I suggested the changes we need to make.

src/pages/signin/ResendValidationForm.js Outdated Show resolved Hide resolved
src/pages/signin/ResendValidationForm.js Outdated Show resolved Hide resolved
@anthony-hull anthony-hull mentioned this pull request Dec 30, 2021
5 tasks
@akshayasalvi
Copy link
Contributor Author

@marcaaron @chiragsalian Is there anything else that needs to be done in this PR from my side?

@chiragsalian
Copy link
Contributor

Nope, not at the moment. I was off a few days for the new years but i'll test/review your PR today 👍

Copy link
Contributor

@chiragsalian chiragsalian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code LGTM and works well. I'm going to merge this PR since it would unblock others waiting on this functionality.
I've left one NAB to address so can you make a follow up for it.

Additionally the screenshot does not match what actually happens so can you update it. The copy between the two are different. This is the copy your PR will actually give,
image

phrase2: 'Money talks. And now that chat and payments are in one place, it\'s also easy.',
phrase3: 'Your payments get to you as fast as you can get your point across.',
phrase4: 'Welcome back to the New Expensify! Please enter your password.',
welcomeBack: 'Welcome back to the New Expensify! Please enter your password.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of these names, especially when i read it in code like so,

`welcomeText.${showPasswordForm ? 'welcomeBack' : 'welcome'}`

Since its confusing to me why if showPasswordForm is true do we want welcomeBack versus welcome. Plus variables should be named after their intention of like what they store and not their parent/class names.

So in this case welcome, wants the phone or email so i would suggest - enterPhoneOrEmail. This becomes welcomeText.enterPhoneOrEmail which is a lot cleaner as welcomeText.welcome is redundant.
As for welcomeBack, it wants the password so i would suggest - enterPassword.

So now the logic becomes,

`welcomeText.${showPasswordForm ? 'enterPassword' : 'enterPhoneOrEmail'}`

which is a lot more verbose and cleaner imo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a NAB - not a blocker.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. In general, feels like the names for these translation keys is a bit all over the place. Wondering if we can maybe come up with some standard guidance for how to name them and then get everyone to apply it in a consistent way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we can maybe come up with some standard guidance for how to name them and then get everyone to apply it in a consistent way?

Yes, love it.

@chiragsalian chiragsalian merged commit e5a3f2c into Expensify:main Jan 3, 2022
@OSBotify
Copy link
Contributor

OSBotify commented Jan 4, 2022

🚀 Deployed to staging by @chiragsalian in version: 1.1.24-19 🚀

platform result
🤖 android 🤖 failure ❌
🖥 desktop 🖥 failure ❌
🍎 iOS 🍎 failure ❌
🕸 web 🕸 failure ❌

@OSBotify
Copy link
Contributor

OSBotify commented Jan 4, 2022

🚀 Deployed to production by @francoisl in version: 1.1.24-19 🚀

platform result
🤖 android 🤖 failure ❌
🖥 desktop 🖥 failure ❌
🍎 iOS 🍎 failure ❌
🕸 web 🕸 failure ❌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants